//////////////////////////////////////////////////////////////////////////////
//
//  Socket model
//
//////////////////////////////////////////////////////////////////////////////
#ifndef MAGUS_SOCKET_HPP
#define MAGUS_SOCKET_HPP

#include <magus/config.hpp>
#include <magus/utils/ReadHandle.hpp>
#include <string>        
#include <cstdio>        
#include <cassert>

#ifdef __MAGUS_WIN32
#include <winsock2.h>    
typedef SOCKET sockdes_t;

#elif defined(__MAGUS_POSIX)
#ifndef _REENTRANT
#define _REENTRANT  
#endif
#include <unistd.h>
#include <netdb.h> 
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <memory.h>
#include <assert.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif
typedef int sockdes_t;
#endif

#ifdef __MAGUS_WIN32
typedef int socklen_t;
typedef unsigned long in_addr_t;
#elif defined(__MAGUS_SUN)
#ifndef _SOCKLEN_T
typedef int socklen_t;
#endif
#ifndef _IN_ADDR_T
typedef unsigned long in_addr_t;
#endif
#endif

namespace magus {
    
typedef struct {
    sockdes_t fd;
    int port;
    int family;
    struct sockaddr_in sa;
    int timeout;  
} socket_t;

namespace 
{
    const int OPR_SUCCESS   =  0;
    const int OPR_ERROR     = -1;
    const int OPR_TIMEUP    = -2;
    const int OPR_EOF       = -3;
    const int GETHOSTBUFLEN = 512;
	const int FILE_BUFLEN	= 8192;		// send&recv file buffer size
}

/*
 * Socket Definition
*/
class Socket
{    
friend class ServerSocket;
    
public: 
    // constructor
    Socket(): _M_fd(INVALID_SOCKET), _M_port(0), _M_timeout(0)
    {
#ifdef __MAGUS_WIN32
        WSADATA wsaData;
        WSAStartup(MAKEWORD(2,2), &wsaData);    
#endif  
    }
    
    // constructor
    Socket(socket_t s) : _M_fd(s.fd), _M_port(s.port), _M_family(s.family), _M_sa(s.sa), _M_timeout(s.timeout) 
    {
#ifdef __MAGUS_WIN32
        WSADATA wsaData;
        WSAStartup(MAKEWORD(2,2), &wsaData);
#endif  
        if (_M_fd != INVALID_SOCKET) {
            setTimeout(_M_timeout);
        }
    }

    // deconstructor
    ~Socket()
    {   
        Close();
#ifdef __MAGUS_WIN32
        WSACleanup();
#endif  
    }

    void Close()
    {
        if (_M_fd != INVALID_SOCKET) {
#ifdef __MAGUS_WIN32
            closesocket(_M_fd);
#elif defined(__MAGUS_POSIX)
            close(_M_fd);
#endif
            _M_fd = INVALID_SOCKET;
        }
    }

    int Connect(const char* host, int port)
    {
        if (_M_fd != INVALID_SOCKET) {
            Close();
        }
        _M_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (_M_fd == INVALID_SOCKET)
        {
            return -1;
        }
        
        in_addr_t addr;
        hostent* hp = NULL;
        int herror = -1;
        if ((addr = inet_addr(host)) != (in_addr_t)INADDR_NONE) {  
            memcpy(&_M_sa.sin_addr.s_addr, &addr, sizeof(addr));    
        }else {
#if defined(__MAGUS_WIN32)
            hp = gethostbyname(host);
#else
            hostent hs;
            char tmp[GETHOSTBUFLEN];
#if defined(__MAGUS_LINUX)
            gethostbyname_r(host, &hs, tmp, GETHOSTBUFLEN-1, &hp, &herror);
#else
            hp = gethostbyname_r(host, &hs, tmp, GETHOSTBUFLEN-1, &herror);
#endif
#endif
            if (hp == NULL)      
                return herror;                   
            memcpy(&_M_sa.sin_addr.s_addr, hp->h_addr_list[0], sizeof(in_addr_t));
        }
        _M_port = port;
        _M_sa.sin_family = AF_INET;
        _M_sa.sin_port = htons((unsigned short)port);
        if (connect(_M_fd, (sockaddr*)&_M_sa, sizeof(_M_sa)) == SOCKET_ERROR) {
            return -1;
        }
        return 0;
    }

    static int Connect(const char* host, int port, socket_t &s)
    {
        s.fd = socket(AF_INET, SOCK_STREAM, 0);
        if (s.fd == INVALID_SOCKET) {
            return -1;
        }
        in_addr_t addr;
        hostent* hp = NULL;
        int herror = -1;
        if ((addr = inet_addr(host)) != (in_addr_t)INADDR_NONE) 
        {  
            memcpy(&s.sa.sin_addr.s_addr, &addr, sizeof(addr));    
        }else {
#if defined(__MAGUS_WIN32)
            hp = gethostbyname(host);
#else
            hostent hs;
            char tmp[GETHOSTBUFLEN];
#if defined(__MAGUS_LINUX)
            gethostbyname_r(host, &hs, tmp, GETHOSTBUFLEN-1, &hp, &herror);
#else
            hp = gethostbyname_r(host, &hs, tmp, GETHOSTBUFLEN-1, &herror);
#endif
#endif
            if (hp == NULL)      
                return herror;                   
            memcpy(&s.sa.sin_addr.s_addr, hp->h_addr_list[0], sizeof(addr));
        }
        s.port = port;
        s.sa.sin_family = AF_INET;
        s.sa.sin_port = htons((unsigned short)port);
        if (connect(s.fd, (sockaddr*)&s.sa, sizeof(s.sa)) == SOCKET_ERROR) {
            return -1;
        }
        return 0;   
    }
    
    int Recv(char *buf, int *len)
    {   
        assert(_M_fd != INVALID_SOCKET);
        int bytes, l;
    
        l = *len;
        while (l > 0) {
    #ifdef __MAGUS_WIN32
            int err_no;
            if ((bytes = recv(_M_fd, buf, l, 0)) == SOCKET_ERROR && 
                (err_no = WSAGetLastError()) == WSAEWOULDBLOCK)
    #elif defined(__MAGUS_POSIX)
        if ((bytes = recv(_M_fd, buf, l, 0)) == -1 && 
            (errno == EINTR || errno == EWOULDBLOCK))
    #endif
            {
                fd_set fdread;
                timeval tv;
                tv.tv_sec = _M_timeout;
                tv.tv_usec = 0;
                FD_ZERO(&fdread);
                FD_SET(_M_fd, &fdread);
                int rv;
    #ifdef __MAGUS_WIN32
                rv = select(_M_fd+1, &fdread, NULL, NULL, &tv);
    #elif defined(__MAGUS_POSIX)
                do{
                    rv = select(_M_fd+1, &fdread, NULL, NULL, &tv);
                }while (rv < 0 && errno == EINTR);
    #endif
                if (rv <= 0) {
                    *len = 0;
                    if (rv == 0)
                        rv = OPR_TIMEUP;
                    return rv;
                }
                continue;
            }
            if (bytes <= 0) {
                *len = 0;
                if (bytes == 0)
                    bytes = OPR_EOF;
                return bytes;
            }
            *len = bytes;
            break;          // read once
        }
        return 0;
    }

    int Send(const char *buf, int len)
    {                                 
        assert(_M_fd != INVALID_SOCKET );
        int bytes;
        int off = 0;
        while (len > 0)
        {
#ifdef __MAGUS_WIN32
            int err_no;
            if ((bytes = send(_M_fd, buf+off, len, 0)) == SOCKET_ERROR && 
                (err_no = WSAGetLastError()) == WSAEWOULDBLOCK)
#elif defined(__MAGUS_POSIX)
            if ((bytes = send(_M_fd, buf+off, len, 0)) == -1 && 
                (errno == EINTR || errno == EWOULDBLOCK))
#endif
            {
                fd_set fdwrite;
                timeval tv;
                tv.tv_sec = _M_timeout;
                tv.tv_usec = 0; 
                FD_ZERO(&fdwrite);
                FD_SET(_M_fd, &fdwrite);
                int rv;
#ifdef __MAGUS_WIN32
                rv = select(_M_fd+1, NULL, &fdwrite, NULL, &tv);
#elif defined(__MAGUS_POSIX)
                do {
                    rv = select(_M_fd+1, NULL, &fdwrite, NULL, &tv);
                }while (rv < 0 && errno == EINTR);
#endif
                if (rv == 0) {
                    return OPR_TIMEUP;
                }else if (rv < 0) {
                    return OPR_ERROR;
                }
                continue;
            }
            if( bytes <= 0) {
                return OPR_ERROR;
            }
            len -= bytes;
            off += bytes;
        } 
        return 0;
    }

    int setTimeout(int timeout)
    {
#ifdef __MAGUS_WIN32
        u_long noblock;
        if (timeout <= 0) 
            noblock = 0;
        else    
            noblock = 1;
        if (ioctlsocket(_M_fd, FIONBIO, &noblock) == SOCKET_ERROR)
            return -1;
        _M_timeout = timeout;
        return 0;
#elif defined(__MAGUS_POSIX)
        int fd_flags;
        if ( (fd_flags = fcntl(_M_fd, F_GETFL, 0)) == -1 )
            return -1;
        if (timeout <= 0) 
            fd_flags &= ~O_NONBLOCK;
        else    
            fd_flags |= O_NONBLOCK;
        if ( fcntl(_M_fd, F_SETFL, fd_flags) == -1)
            return -1;
        _M_timeout = timeout;
        return 0;   
#endif
    }

    bool isConnected() 
    { 
        return (_M_fd != INVALID_SOCKET); 
    }

    std::string getHostName()
    {
        if (_M_fd == INVALID_SOCKET) {
            return std::string("");
        }
        struct hostent* hp;
    #ifdef __MAGUS_WIN32                                                                                                                    
        hp = gethostbyaddr((char *)&_M_sa.sin_addr, sizeof(struct in_addr), AF_INET);
    #else
        struct hostent hs;
        char tmp[GETHOSTBUFLEN];
        int herror;
      #if defined(__MAGUS_LINUX)
        gethostbyaddr_r((char *)&_M_sa.sin_addr, sizeof(struct in_addr), AF_INET,
                &hs, tmp, GETHOSTBUFLEN-1, &hp, &herror);
      #else
        hp = gethostbyaddr_r((char *)&_M_sa.sin_addr, sizeof(struct in_addr), AF_INET,
                &hs, tmp, GETHOSTBUFLEN, &herror);
      #endif    /* !__MAGUS_LINUX */
    #endif  /* !__MAGUS_WIN32 */
    
        if (hp == NULL) {
            return getHostAddress();
        }
        return std::string(hp->h_name);
    }
    
    std::string getHostAddress()
    {
        if (_M_fd == INVALID_SOCKET) {
            return std::string("");
        }
    #ifdef __MAGUS_WIN32                                                                                                                    
        char *ipaddr = inet_ntoa(_M_sa.sin_addr);
        return std::string(ipaddr);
    #else
        char buf[INET_ADDRSTRLEN];      /* space for 255.255.255.255\0 */
        inet_ntop(AF_INET, &_M_sa.sin_addr, buf, INET_ADDRSTRLEN);
        return std::string(buf);
    #endif  /* !__MAGUS_WIN32 */
    }
    
    int setSendBufferSize(int size)
    {
        if (size < 0) 
            return -1;
        int val = size;
        int len = sizeof(val);
        if (setsockopt(_M_fd, SOL_SOCKET, SO_SNDBUF, (const char*)&val, len) != 0)
            return -1;
        return 0;
    }
    
    int setRecvBufferSize(int size)
    {
        if (size < 0) 
            return -1;
        int val = size;
        int len = sizeof(val);
        if (setsockopt(_M_fd, SOL_SOCKET, SO_RCVBUF, (const char*)&val, len) != 0)
            return -1;
        return 0;
    }
    
    int setSoLinger(bool on, int seconds)
    {   
        struct linger val;
        val.l_onoff = on;
        val.l_linger = seconds;
        int len = sizeof(val);
        if (setsockopt(_M_fd, SOL_SOCKET, SO_LINGER, (const char*)&val, len) != 0)
            return -1;
        return 0;   
    }
    
    int setKeepAlive(bool on)
    {   
        int val = on;
        int len = sizeof(val);
        if (setsockopt(_M_fd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&val, len) != 0)
            return -1;
        return 0;   
    }
    
    int setTcpNoDelay(bool on)
    {   
        int val = on;
        int len = sizeof(val);
        if (setsockopt(_M_fd, SOL_SOCKET, TCP_NODELAY, (const char*)&val, len) != 0)
            return -1;
        return 0;   
    }
    
    //////////////////////////////////////////////////////////////////////////////
    // Get functions
    int getSendBufferSize()
    {
        int val;
        int len = sizeof(val);
        if (getsockopt(_M_fd, SOL_SOCKET, SO_SNDBUF, (char *)&val, (socklen_t*)&len) != 0)
            return -1;
        return len;
    }
    
    int getRecvBufferSize()
    {
        int val;
        int len = sizeof(val);
        if (getsockopt(_M_fd, SOL_SOCKET, SO_RCVBUF, (char*)&val, (socklen_t*)&len) != 0)
            return -1;
        return len;
    }
    
    int getSoLinger()
    {   
        struct linger val;
        int len = sizeof(val);
        if (getsockopt(_M_fd, SOL_SOCKET, SO_LINGER, (char *)&val, (socklen_t*)&len) != 0)
            return -1;
        if (val.l_onoff == 0)
            return 0;
        return val.l_linger;    
    }
    
    int getKeepAlive()
    {   
        int val;
        int len = sizeof(val);
        if (getsockopt(_M_fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&val, (socklen_t*)&len) != 0)
            return -1;
        return val; 
    }
    
    int getTcpNoDelay()
    {   
        int val;
        int len = sizeof(val);
        if (getsockopt(_M_fd, SOL_SOCKET, TCP_NODELAY, (char*)&val, (socklen_t*)&len) != 0)
            return -1;
        return val; 
    }
          
    int getTimeout() { return _M_timeout; }

    int getFD() { return _M_fd; }

    int getPort() { return _M_port; }

    int getFamily() { return _M_family; }

    sockaddr_in getInetAddress() { return _M_sa; }

    // readers
    int readBytes(char *buf, int len)
    {
        int n, rv;
        int off = 0;
        while (len > 0) {
            n = len;
            rv = Recv(buf+off, &n);
            if (rv != 0)
                return rv;
            len -= n;
            off += n;
        }
        return 0;
    }

    int readByte(char *i) {
        int len = 1;
		char c;
        int rv = Recv(&c, &len);
		(*i) = c;
		return rv;
    }

    int readBoolean(bool *i) {
        int len = 1;
        return Recv((char *)i, &len);
    }

    int readShort(short *i)
    {
#if (__MAGUS_BYTE_ORDER == __MAGUS_BIG_ENDIAN)          
        return readBytes((char *)i, 2);
#else
        char buf[2];
        int rv = readBytes(buf, 2);
        (*i) = shortGet(buf, 0);
        return rv;
#endif
    }

    int readInt(int *i)
    {
#if (__MAGUS_BYTE_ORDER == __MAGUS_BIG_ENDIAN)          
        return readBytes((char *)i, 4);
#else
        char buf[4];
        int rv = readBytes(buf, 4);
        (*i) = longGet(buf, 0);
        return rv;
#endif
    }

    int readInt(long *i)
    {
#if (__MAGUS_BYTE_ORDER == __MAGUS_BIG_ENDIAN)          
        return readBytes((char *)i, 4);
#else
        char buf[4];
        int rv = readBytes(buf, 4);
        (*i) = longGet(buf, 0);
        return rv;
#endif
    }

    int readFloat(float *i)
    {
#if (__MAGUS_BYTE_ORDER == __MAGUS_BIG_ENDIAN)          
        return readBytes((char *)i, 4);
#else
        char buf[4];
        int rv = readBytes(buf, 4);
        (*i) = floatGet(buf, 0);
        return rv;
#endif
    }

    // writers
    int writeByte(char i) 
    {
		char c = (i & 0xff);
        return Send(&c, 1);
    }

    int writeBoolean(bool i) 
    {
        return Send((char *)&i, 1);
    }
    
    int writeShort(short i)
    {
#if (__MAGUS_BYTE_ORDER == __MAGUS_BIG_ENDIAN)          
        return Send((char *)&i, 2);
#else
        char buf[2];
        shortStore(buf, i);
        return Send(buf, 2);
#endif
    }

    int writeInt(int i)
    {
#if (__MAGUS_BYTE_ORDER == __MAGUS_BIG_ENDIAN)          
        return Send((char *)&i, 4);
#else
        char buf[4];
        longStore(buf, i);
        return Send(buf, 4);
#endif
    }
    
    int writeInt(long i)
    {
#if (__MAGUS_BYTE_ORDER == __MAGUS_BIG_ENDIAN)          
        return Send((char *)&i, 4);
#else
        char buf[4];
        longStore(buf, i);
        return Send(buf, 4);
#endif
    }

    int writeFloat(float i)
    {
#if (__MAGUS_BYTE_ORDER == __MAGUS_BIG_ENDIAN)          
        return Send((char *)&i, 4);
#else
        char buf[4];
        floatStore(buf, i);
        return Send(buf, 4);
#endif
    }
    
    // read a UTF format string to std::string
    int readUTF(std::string& str)
    { 
        char buf[1024];
        int n;  
        
        short len = 0;
        int rv = readShort(&len);
        if (rv != 0)
            return rv;
        str = "";
        while (len > 0) {
            n = ( len > 1024 ? 1024 : len );
            rv = Recv(buf, &n);
            if (rv != 0)
                return rv;
            str.append(buf, n);
            len -= n;
        }
        return 0;
    }
    
    // read a UTF string to char buffer
    int readUTF(char *buf, int buflen, int *len)
    { 
        short n = 0;
        int rv = readShort(&n);
        if (rv != 0)
            return rv;
        if (n < 0 || n > buflen)
            return OPR_ERROR;
        *len = n;
        return readBytes(buf, *len);
    }
    
    // write a UTF format from std::string
    int writeUTF(const std::string& str)
    {
        short n = str.length(); 
        int rv = writeShort(n);
        if (rv == 0 && n > 0)
            rv = Send(str.data(), n);
        return rv;
    }
    
    // write a UTF format string from char buffer
    int writeUTF(const char* buf, int len)
    {
        short n = len;
        int rv = writeShort(n);
        if (rv == 0 && n > 0)
            rv = Send(buf, n);
        return rv;
    }

    // read a UTF string to char buffer
    int readPacket(char* buf, int buflen, int *len)
    { 
        int rv = readInt(len);
        if (rv != 0)
            return rv;
        if (*len < 0 || *len > buflen)
            return OPR_ERROR;
        return readBytes(buf, *len);
    }

    // write a UTF format string from char buffer
    int writePacket(const char* buf, int len)
    {
        int rv = writeInt(len);
        if (rv == 0 && len > 0)
            rv = Send(buf, len);
        return rv;
    }

	int recvFile(FILE* fp, int limit)
	{
		char buf[FILE_BUFLEN];
		int len, total = 0;
		int rv = 0;
		while (rv == 0) 
		{
		    len = FILE_BUFLEN;
			rv = Recv(buf, &len);
			if (rv < 0) {
				if (rv == OPR_EOF)	// end of file reached
					rv = 0;
				break;
			}
			total += len;
			if (total > limit) {
				rv = -1;
				break;
			}
			rv = fwrite(buf, len, 1, fp) - 1;
		}
		return rv;		     
	}

	int sendFile(FILE* fp)
	{
		char buf[FILE_BUFLEN];
		int len;
	    int rv = 0;
		while (rv == 0) 
		{
			len = fread(buf, 1, FILE_BUFLEN, fp);
			if (len <= 0)     // 0 means end of file
				return len;
			rv = Send(buf, len);
		}
		return rv;
	}
	
private:
    sockdes_t           _M_fd;
    int                 _M_port;
    int                 _M_family;
    struct sockaddr_in  _M_sa;
    int                 _M_timeout;  
};      

} // namespace magus

// Change Log:
//    02 JLY 03  Initial version.

#endif  /* MAGUS_SOCKET_HPP */

